package lumis.report.xmlgeneration;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;

import lumis.portal.PortalException;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.monitor.IAggregationType;
import lumis.portal.service.ServiceConfig;
import lumis.portal.transaction.PortalTransactionFactory;
import lumis.report.IJRDataSourceProvider;
import lumis.report.util.ReportUtil;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRMapCollectionDataSource;

/**
 * Data source provider used XML Generation report.
 * 
 * @version $Revision: 13092 $ $Date: 2011-05-28 18:19:06 -0300 (Sat, 28 May 2011) $
 * @since 6.0.0
 */
public class XmlGenerationJRDataSourceProvider implements IJRDataSourceProvider
{
	/**
	 * Reads the data from persistence.
	 * @param startDate the start of the period to be read from. If null
	 * 30 days previous to the current day will be used. The time part of the
	 * date is ignored.
	 * @param endDate the end of the period to be read from. If null the current
	 * day will be used. The time part of the date is ignored.
	 * @return the data rows.
	 * @since 6.0.0
	 */
	@SuppressWarnings("unchecked")
	private static List<Map<String,Object>> readData(Date startDate, Date endDate) throws JRException
	{
		//if start date and end date was not specified, it sets default values ( 30 days before as start and the actual date as end )
		Calendar calendar = Calendar.getInstance();
		if (startDate == null)
		{
			calendar.add(Calendar.DATE, -30);
			startDate = calendar.getTime();
		}
		if (endDate == null)
		{
			endDate = new Date (System.currentTimeMillis());
		}
		//truncate endDate and startDate
		startDate = ReportUtil.truncateDateTime(startDate);
		calendar.setTime(endDate);
		calendar.add(Calendar.DATE, 1);
		endDate = ReportUtil.truncateDateTime(calendar.getTime());
		//adds 1 day to endDate, to include the end day
		endDate = new Date (endDate.getTime() - 1);	
		
		EntityManager entityManager = ManagerFactory.getEntityManager();
				
		String query = 
		"SELECT " +
		"serviceLogic.average - xslTransformation.drt as processingTime, "+
		"xslTransformation.channel, "+
		"xslTransformation.pageName, "+
		"xslTransformation.serviceInterface "+
		//processing time = total time (from subquery) - xsl transformation time
		"FROM (SELECT SUM(durationMeasure.sumOfValues)/SUM(durationMeasure.hits) AS drt,"+
		"channelAggregation.value as channel, "+
		"pageNameAggregation.value as pageName, "+
		"serviceInstanceAggregation.value as serviceInterface, "+
		"serviceInterfaceInstanceAgg.value as serviceInterfaceId "+
		
		"FROM lum_MonEventData eventData "+
		"INNER JOIN lum_MonEvent event ON eventData.eventId = event.id "+
		
		//joins to retrieve value for duration of the transformation
		"INNER JOIN lum_MonEventDataMeasure durationMeasure ON durationMeasure.eventDataId = eventData.id INNER JOIN lum_MonMeasureType durationMeasureType "+
		"ON durationMeasure.measureTypeId = durationMeasureType.id "+
		"AND durationMeasureType.measureTypeKey = 'lumis.portal.monitor.mt.duration.ms' "+
		//indicating that the event is a xsl transformation event
		"AND event.eventKey = 'lumis.portal.servicecontainer.ev.serviceinterfaceinstance.xslt' "+
		

		/*
		 * for each field that is needed for this event, add and adapted version of this query part
		 * INNER JOIN lum_MonEventDataAggregation XXXXXXXXAggregation ON XXXXXXXXAggregation.eventDataId = eventData.id
		 * INNER JOIN lum_MonAggregationType XXXXXXXXAggregationType ON XXXXXXXXAggregation.aggregationTypeId = XXXXXXXXAggregationType.id 
		 * AND XXXXXXXXAggregationType.aggregationTypeKey = '<aggregation key>'
		 * 
		 * to use, in the select clause, add the field: XXXXXXXXAggregation.value as YYYYY
		 */
		
		//joins to retrieve the channel where the xsl transformation occurred 
		"INNER JOIN lum_MonEventDataAggregation channelAggregation "+
		"ON channelAggregation.eventDataId = eventData.id "+
		"INNER JOIN lum_MonAggregationType channelAggregationType "+
		"ON channelAggregation.aggregationTypeId = channelAggregationType.id "+
		"AND channelAggregationType.aggregationTypeKey = '" + IAggregationType.AGGREGATION_TYPE_CHANNEL_PATH + "' "+
		
		//joins to retrieve the name of the page where the xsl transformation occurred 
		"INNER JOIN lum_MonEventDataAggregation pageNameAggregation "+
		"ON pageNameAggregation.eventDataId = eventData.id "+
		"INNER JOIN lum_MonAggregationType pageNameAggregationType "+
		"ON pageNameAggregation.aggregationTypeId = pageNameAggregationType.id "+
		"AND pageNameAggregationType.aggregationTypeKey = '" + IAggregationType.AGGREGATION_TYPE_PAGE_NAME + "' "+
		
		//joins to retrieve the serviceInterfaceInstaceId
		"INNER JOIN lum_MonEventDataAggregation serviceInterfaceInstanceAgg "+
		"ON serviceInterfaceInstanceAgg.eventDataId = eventData.id "+
		"INNER JOIN lum_MonAggregationType serviceInterfaceInstanceAggTp "+
		"ON serviceInterfaceInstanceAgg.aggregationTypeId = serviceInterfaceInstanceAggTp.id "+
		"AND serviceInterfaceInstanceAggTp.aggregationTypeKey = '"+ IAggregationType.AGGREGATION_TYPE_SERVICE_INTERFACE_INSTANCE_ID +"' "+
		"AND event.eventKey = 'lumis.portal.servicecontainer.ev.serviceinterfaceinstance.xslt' "+
		//joins to retrieve the serviceInstaceId
		"INNER JOIN lum_MonEventDataAggregation serviceInstanceAggregation "+
		"ON serviceInstanceAggregation.eventDataId = eventData.id "+
		"INNER JOIN lum_MonAggregationType serviceInstanceAggregationType "+
		"ON serviceInstanceAggregation.aggregationTypeId = serviceInstanceAggregationType.id "+
		"AND serviceInstanceAggregationType.aggregationTypeKey = '"+ IAggregationType.AGGREGATION_TYPE_SERVICE_INTERFACE_ID +"' "+
		"WHERE startOfPeriod >= ? AND endOfPeriod <= ? " +
		"GROUP BY channelAggregation.value, pageNameAggregation.value, serviceInstanceAggregation.value, serviceInterfaceInstanceAgg.value " +
		") xslTransformation "+
		/*
		 * Creating join with associated event. 
		 * RENDERIZATIONS GENERATING TRANSFORMATIONS. 
		 * Due to database modeling/aggregations and events, it happens as separate consolidated entries and thus needs to be co-ralated by 
		 * serviceInterfaceInstaceId, Start Time and End Time. 
		 */
		"INNER JOIN "+
		//sub-query to retrieve associated renderization event.  exposes only needed columns 
		"(SELECT serviceInterfaceInstanceAggr.value as interfaceInstanceId, sum(durationMsr.sumOfValues)/sum(durationMsr.hits) AS average "+
		" FROM lum_MonEventData EventDt "+
		//joins to retrieve the renderization event
		"INNER JOIN lum_MonEvent Ev ON EventDt.eventId = Ev.id and Ev.eventKey = 'lumis.portal.servicecontainer.ev.serviceinterfaceinstance.renderization' "+
		"INNER JOIN lum_MonEventDataMeasure durationMsr ON durationMsr.eventDataId = EventDt.id INNER JOIN lum_MonMeasureType durationMsrType "+
		"ON durationMsr.measureTypeId = durationMsrType.id "+
		//retrieving the duration measure  
		"AND durationMsrType.measureTypeKey = 'lumis.portal.monitor.mt.duration.ms' "+
		
		//joins to retrieve serviceInterfaceInstanceId to create co-relation with external query
		"INNER JOIN lum_MonEventDataAggregation serviceInterfaceInstanceAggr "+
		"ON serviceInterfaceInstanceAggr.eventDataId = EventDt.id "+
		"INNER JOIN lum_MonAggregationType serviceInterfaceInstanceAggrType "+
		"ON serviceInterfaceInstanceAggr.aggregationTypeId = serviceInterfaceInstanceAggrType.id "+
		"AND serviceInterfaceInstanceAggrType.aggregationTypeKey = '"+ IAggregationType.AGGREGATION_TYPE_SERVICE_INTERFACE_INSTANCE_ID +"' "+
		"WHERE startOfPeriod >= ? AND endOfPeriod <= ? " +
		"group by serviceInterfaceInstanceAggr.value "+
		") serviceLogic " +
	
		"ON serviceLogic.interfaceInstanceId = xslTransformation.serviceInterfaceId "+
		"order by processingTime desc;";
		
		
		List<Object[]> interfacesGenerated = entityManager.createNativeQuery(query) 	
				.setParameter(1, startDate)
				.setParameter(2, endDate)
				.setParameter(3, startDate)
				.setParameter(4, endDate)
				.getResultList();
		
		List<Map<String, Object>> result = new ArrayList<Map<String,Object>>();
		for(Object[] interfaceGenerated : interfacesGenerated)
		{
			//sets the parameters to be sent to Jasper
			Map<String, Object> parameters = new HashMap<String, Object>();
			parameters.put("processingTime", interfaceGenerated[0]);
			parameters.put("channel", interfaceGenerated[1]);
			parameters.put("pageName", interfaceGenerated[2]);
			parameters.put("serviceInterface", interfaceGenerated[3]);
			
			String serviceName = null;
			try
			{
				//gets the serviceInterfaceId to find out the service name
				String interfaceId = (String)interfaceGenerated[3];
				String serviceId = interfaceId.substring(0, interfaceId.lastIndexOf('.'));
				ServiceConfig serviceConfig = ManagerFactory.getServiceManager().get(SessionConfig.getCurrentSessionConfig(), serviceId, PortalTransactionFactory.getCurrentTransaction());
				serviceName = serviceConfig.getName();
				serviceName = ManagerFactory.getLocalizationManager().localize(serviceName, serviceConfig.getResource());
			}
			catch (Exception e)
			{
				throw new JRException(e.getMessage(),e);
			}	
			parameters.put("serviceName", serviceName);
		
			result.add(parameters);			
		}		
		return result;
	}
	
	/**
	 * Returns the data source to be used in the table of the report.
	 * @param startDate the start of the period to be read from. If null
	 * 30 days previous to the current day will be used. The time part of the
	 * date is ignored.
	 * @param endDate the end of the period to be read from. If null the current
	 * day will be used. The time part of the date is ignored.
	 * @return the data source.
	 * @since 6.0.0
	 */
	public static JRDataSource getDataForTable(final Date startDate, final Date endDate) throws JRException
	{
		return new JRMapCollectionDataSource(readData(startDate, endDate));
	}
	
	public JRDataSource create(JasperReport jasperreport, Map<String, Object> parameters) throws JRException, PortalException
	{
		final Date startDate = (Date) parameters.get("startDate");
		final Date endDate = (Date) parameters.get("endDate");
		
		return getDataForTable(startDate, endDate);
	}

	public void dispose(JRDataSource jrDataSource) throws JRException, PortalException
	{
		
	}
}
